diagnostics_channel: add opt-in subscriber suppression via bypassBy and bypassed()#63651
diagnostics_channel: add opt-in subscriber suppression via bypassBy and bypassed()#63651DivyanshuX9 wants to merge 34 commits into
Conversation
Defer non-critical warnings to the next event loop iteration when can_call_into_js() returns false. This prevents crashes when V8 emits warnings during REPL preview evaluation or other contexts where JavaScript execution is temporarily forbidden. When a warning is emitted inside DisallowJavascriptExecutionScope, ProcessEmitWarningGeneric cannot be called immediately. Instead, use env->SetImmediate() to queue the warning emission for after the scope exits. This preserves full warning formatting, deprecation codes, and --redirect-warnings routing. Signed-off-by: Divyanshu Sharma <Divyanshu88999@gmail.com>
Refs: nodejs#63623 Refs: nodejs#63651 Signed-off-by: Divyanshu Sharma <divyanshu88999@gmail.com>
5b4110a to
8b122c2
Compare
Refs: nodejs#63623 Refs: nodejs#63651 Signed-off-by: Divyanshu Sharma <divyanshu88999@gmail.com>
Refs: nodejs#63623 Refs: nodejs#63651 Signed-off-by: Divyanshu Sharma <divyanshu88999@gmail.com>
8b122c2 to
e4aea85
Compare
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #63651 +/- ##
==========================================
+ Coverage 90.25% 90.26% +0.01%
==========================================
Files 741 741
Lines 241001 241148 +147
Branches 45399 45445 +46
==========================================
+ Hits 217506 217671 +165
- Misses 15060 15062 +2
+ Partials 8435 8415 -20
🚀 New features to boost your workflow:
|
Qard
left a comment
There was a problem hiding this comment.
Tests are missing a lot of necessary common.mustCall(fn) wrappers.
Also, it seems like this was just vibe-coded without reviewing the output before submitting the PR. Please ensure it is in a good state and that the test suite and lint passes before submitting.
Signed-off-by: Divyanshu Sharma <divyanshu88999@gmail.com>
Signed-off-by: Divyanshu Sharma <divyanshu88999@gmail.com>
Signed-off-by: Divyanshu Sharma <divyanshu88999@gmail.com>
Signed-off-by: Divyanshu Sharma <divyanshu88999@gmail.com>
…update tests to use common.mustCall
|
@Qard , thanks for the detailed review. So far the checklist of what i have fixed in the latest push: [] Removed the impossible ALS try/catch fallback Ready for re-review and any changes if you want, when you have time please review it. |
0f6e780 to
8fa6fd8
Compare
Signed-off-by: Divyanshu Sharma <divyanshu88999@gmail.com>
8fa6fd8 to
b8194af
Compare
|
The node_errors.cc change still seems to be present. |
…ove SetImmediate deferral) Signed-off-by: Divyanshu Sharma <divyanshu88999@gmail.com>
Signed-off-by: Divyanshu Sharma <divyanshu88999@gmail.com>
|
@DivyanshuX9 It would also be appreciated to get a proper response and assessment from you and not from AI. If I want to ask AI I have an API key for that. You own this PR, you reply. |
|
first of all @rochdev ok i agree with you that using one ID for the subscriber reference would be just as valid for doing the check as well, but your point is still correct and should be noted. The justification for the one ID vs. 3-line userland style is in the ability to propagate the ALS Context automatically through the Promises, setImmediate, and queueMicrotask chains in the exportTracesToBackend function Calls when doing: bypass(kDatadog, () => exportTracesToBackend());In this case, using jsbypass will automatically carry the context through all of the async boundaries without having to do anything extra. However, if you were to do this using the userland style, you would have to thread the ALS context through every async processing boundary, and that's where aint many vendors may do so well. On the multiple ID's: the Set-style implemntation is already supporting this feature. Different keys for different vendors will co-exist in a manner where the two vendors will not interfere with each other, which is hard to accomplish using a single subscriber reference check. ALso said that, I do believe your concern about the complexity to savings ratio is valid; however, at the end of the day, it will be up to @bengl and @BridgeAR if this feature fits into the core of the framework. I don't have a strong opinion one way or the other on this; I implemented what was asked in the Issue. Also if you want you can directly go to the issue #63623 for more context regrading the issue and feature asked |
Signed-off-by: Divyanshu Sharma <divyanshu88999@gmail.com>
ea57daf to
614bc7e
Compare
Signed-off-by: Divyanshu Sharma <divyanshu88999@gmail.com>
|
@bengl both items from your review are addressed. On the TracingChannel side, I deep-dived into this, and the per subscriber bypassId already works in TracingChannel without any additional code in traceSync, tracePromise, or traceCallback because the bypassId gets passed from TracingChannel.subscribe() down through BoundedChannel.subscribe() to its underlying Channel.subscribe() where it’s stored in _bypassSubscribers. All code paths in the 3 trace methods will hit ActiveChannel.publish() which performs the per handler bypassId check including the async continuations in tracePromise and traceCallback. Same for stores via RunStoresScope. I also added tests 11, 12, and 13 to verify this for traceSync, tracePromise, and traceCallback, all tests 11-13 pass. Tests 12 & 13 specifically provide coverage for async boundaries to ensure bypass context is propagated correctly through the continuation window. A note of caution: the convenience function subscribe(name, fn) does NOT support options in this PR, and bypassId would only be accessible via the instance API (ch.subscribe(fn, { bypassId })). This is done on purpose as bypass is essentially an advanced APM vendor feature and those vendors are already using the instance API pattern. If the team would like, I would be glad to add options support to the module-level helpers in a subsequent PR. What could make sense as next steps from here: -Docs update in doc/api/diagnostics_channel.md covering bypass() and the bypassId option with a real-world APM example. -Mirroring this in dc-polyfill so Bun and Deno users aren't stuck waiting for runtime support. Happy to pick up any of those or adjust anything here based on your feedback. |
Signed-off-by: Divyanshu Sharma <divyanshu88999@gmail.com>
|
@bengl @BridgeAR
Still waiting on your re-review when you have time. Let me know if there's anything specific blocking approval so I can address it. |
|
@DivyanshuX9 Thanks for the additional details. I was trying to figure out whether Additional docs are definitely needed though. |
| if (index === -1) return false; | ||
| // Check normal subscribers first | ||
| let index = -1; | ||
| for (let i = 0; i < this._subscribers.length; i++) { |
There was a problem hiding this comment.
What is the reason for moving from ArrayPrototypeIndexOf to a for loop?
There was a problem hiding this comment.
oopsie , i think i just need to address the bypass subscribers since they only contains that id and handler
ok on it
|
I think implementation + tests + docs of a new public API should come in one PR. |
|
@Flarna Well once BridgeAR also mentioned about doc
at that time was not sure how far reviews will go ,also @rochdev wanted some examples rightaway, So made a seperate PR for that to keep this one clean , yes if i get a green signal to land this one will will update and merge that pr doc in this one. |
Signed-off-by: Divyanshu Sharma <divyanshu88999@gmail.com>
|
@Flarna i have moved all the docs here |
| <!-- YAML | ||
| added: REPLACEME | ||
| --> | ||
|
|
There was a problem hiding this comment.
Maybe mark it as Stability: 1.1 - Active development (or experimental), similar as tracing channels.
|
|
||
| // Use bypass() to skip opted-in subscribers during internal calls | ||
| const kMyTool = Symbol('my-tool'); | ||
| diagnostics_channel.channel('my-channel').subscribe(onMessage, { bypassId: kMyTool }); |
There was a problem hiding this comment.
I find this additon a bit misplaced after the unsubscribe.
I think it's not needed to include bypass in this intro sample as I don't think it is part of the typical usages.
There are several other APIs not included here.
| added: REPLACEME | ||
| --> | ||
|
|
||
| * `key` {symbol|Object} The bypass identity token. Must match the |
There was a problem hiding this comment.
I find "Must match the bypassId used when subscribing." a bit confusing/inaccurate.
Several users might subscribe different or the same channels with different keys. There is no requirement to match.
The suppression happens only for matching keys like described below.
| * `...args` {any} Optional arguments to pass to the function. | ||
| * Returns: {any} The return value of `fn`. | ||
|
|
||
| Runs `fn` with the given `key` active in the current async context. |
There was a problem hiding this comment.
I think the wording "active in the current async context" is missleading. It's not accessible to anyone as it is only in the interal store.
Maybe remove this implemenation detail from the docs and focus on describing the behavior only.
| `{ bypassId: key }` will be skipped for the duration of `fn` and | ||
| any async continuations (Promises, timers, microtasks) within it. | ||
|
|
||
| This is designed for observability tooling (APM agents, tracers) |
There was a problem hiding this comment.
not sure if this sentence provides any value here.
| const kMyTracer = Symbol('my-tracer'); | ||
|
|
||
| // Subscribe to HTTP requests, but opt into bypass | ||
| channel('http.client.request').subscribe((message) => { |
There was a problem hiding this comment.
maybe use http.client.request.start as http.client.request doesn't seem to exist.
| @@ -461,6 +627,34 @@ channel.subscribe((message, name) => { | |||
| }); | |||
| ``` | |||
|
|
|||
| To opt this subscriber into bypass behavior: | |||
There was a problem hiding this comment.
I don't think it's needed to repeat the sample here, a the link to should be enough (same applies below)
| @@ -948,23 +1237,20 @@ added: | |||
| - v19.9.0 | |||
| - v18.19.0 | |||
| changes: | |||
| - version: REPLACEME | |||
There was a problem hiding this comment.
That change seems wrong/unrelated
Maybe some issue during rebase?
There was a problem hiding this comment.
hmm , i see its bit different from doc PR
Signed-off-by: Divyanshu Sharma <divyanshu88999@gmail.com>
2ba3a7a to
f7f593e
Compare
|
@Flarna , lowkey I have addressed all feedback in latest push:
Thank you for the thorough review. |
also @Flarna at 251 line is this correct or should i just remove REPLACE ME with the stability:1.1 right away |
|
No, the The stability index is just documentation and independent of that. |
Summary
Adds opt-in subscriber suppression to
diagnostics_channelso APMagents can prevent recursive instrumentation when their own internal
code calls into instrumented libraries.
What Changed
lib/diagnostics_channel.jssuppressed(key, fn, thisArg, ...args)- runsfnwiththe given key active in the current async context
subscribe()andbindStore()accept{ subscriberId }optionpath has zero overhead, ALS is only hit when bypass subscribers exist
AsyncLocalStorageinitialization viagetSuppressionsStorage()async_hooks is available
validateBypassKey()helper for all key validationtest/parallel/test-diagnostics-channel-suppression.jsstore, nested, and type validation cases
Why
APM agents currently implement suppression themselves using a
custom ALS noop marker - every vendor reimplements the same
pattern differently. Moving it into core:
Backward Compatibility
Naming
As per @bengl final name is "Bypass"
Addresses:#63623